package fr.inserm.umr915.vcfannotator;

import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;

import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.SecondaryConfig;
import com.sleepycat.je.SecondaryCursor;
import com.sleepycat.je.SecondaryDatabase;
import com.sleepycat.je.SecondaryKeyCreator;

import fr.inserm.umr915.libjson.Node;
import fr.inserm.umr915.libjson.ObjectNode;
import fr.inserm.umr915.libjson.bdb.NodeBinding;

public class BaseDatabase
	{
	private final static Logger LOG=Logger.getLogger("vcfannotator");
	private File dbHome=null;
	private boolean dropEnvironmentOnClose=false;
	private Environment environment=null;
	private boolean readOnly=false;
	private Database json2json=null;
	private SecondaryDatabase snpDatabase=null;
	
	private class VariantKeyCreator 
		implements SecondaryKeyCreator
		{
		private NodeBinding nodeBinding=new NodeBinding();
		private Variant.Binding varBinding=new Variant.Binding();
		@Override
		public boolean createSecondaryKey(
				SecondaryDatabase db,
				DatabaseEntry key1,
				DatabaseEntry value1,
				DatabaseEntry result)
			{
			Node n= this.nodeBinding.entryToObject(key1);
			if(!n.isObject()) return false;
			ObjectNode object=n.asObjectNode();
			if(!object.getMap().containsKey("_class"))
				{
				LOG.info("no key _class");
				return false;
				}
			if(!object.get("_class").asStringNode().getValue().equals("variation"))
				{
				LOG.info("not a variation");
				return false;
				}
			Variant var=new Variant(
				object.get("build").asStringNode().getValue(),
				object.get("chrom").asStringNode().getValue(),
				object.get("pos1").asIntNode().getValue(),
				object.get("ref").asStringNode().getValue(),
				object.get("alt").asStringNode().getValue()
				);
			this.varBinding.objectToEntry(var, result);
			return true;
			}
		}
	
	
	public BaseDatabase()
		{
		
		}
	
	public Database getJsonDatabase()
		{
		return json2json;
		}
	
	public SecondaryDatabase getVariantDatabase()
		{
		return snpDatabase;
		}
	
	public Environment getEnvironment()
		{
		return environment;
		}
	
	public void setEnvHome(File dbHome)
		{
		this.dbHome = dbHome;
		if(!dbHome.isDirectory()) throw new IllegalArgumentException("not a directory "+dbHome);
		}
	
	public Set<String> getBuilds()
		{
		Set<String> set=new HashSet<String>();
		SecondaryCursor c=null;
		try {
			Variant.Binding varBinding=new Variant.Binding();
			DatabaseEntry key=new DatabaseEntry();
			DatabaseEntry data=new DatabaseEntry();
			c=this.snpDatabase.openCursor(null, null);
			while(c.getNext(key, data, LockMode.DEFAULT)==OperationStatus.SUCCESS)
				{
				set.add(varBinding.entryToObject(key).getBuild());
				}
			return set;
			}
		finally
			{
			if(c!=null) c.close();
			}
		}
	
	public Set<String> getChromosomes(String build)
		{
		Set<String> set=new HashSet<String>();
		SecondaryCursor c=null;
		try {
			Variant.Binding varBinding=new Variant.Binding();
			DatabaseEntry key=new DatabaseEntry();
			DatabaseEntry data=new DatabaseEntry();
			c=this.snpDatabase.openCursor(null, null);
			while(c.getNext(key, data, LockMode.DEFAULT)==OperationStatus.SUCCESS)
				{
				Variant v=varBinding.entryToObject(key);
				if(!v.getBuild().equals(build)) continue;
				set.add(v.getChrom());
				}
			return set;
			}
		finally
			{
			if(c!=null) c.close();
			}
		}
	 
	public synchronized void open() throws IOException
		{
		close();
		if(this.dbHome==null)
			{
			if(this.readOnly) throw new IllegalStateException("cannot create a readonly tmp database");
			File f=File.createTempFile("_tmpvcfannot", ".dir");
			if(!f.delete()) throw new IOException("cannot delete "+f);
			if(!f.mkdir()) throw new IOException("cannot create tmpdir "+f);
			LOG.info("tmp dir is "+f);
			this.dbHome=f;
			this.dropEnvironmentOnClose=true;
			}
		EnvironmentConfig dcfg=new EnvironmentConfig();
		dcfg.setReadOnly(readOnly);
		dcfg.setAllowCreate(!readOnly);
		dcfg.setConfigParam(EnvironmentConfig.LOG_FILE_MAX,"250000000");
		this.environment=new Environment(this.dbHome, dcfg);
		
		DatabaseConfig cfg=new DatabaseConfig();
		cfg.setReadOnly(readOnly);
		cfg.setAllowCreate(!readOnly);
		this.json2json=this.environment.openDatabase(null, "json2json", cfg);
		
		SecondaryConfig  cfg2=new SecondaryConfig();
		cfg2.setBtreeComparator(Variant.Sorter.class);
		cfg2.setKeyCreator(new VariantKeyCreator());
		cfg2.setReadOnly(readOnly);
		cfg2.setAllowCreate(!readOnly);
		this.snpDatabase=this.environment.openSecondaryDatabase(null, "variants2db", this.json2json, cfg2);
		}
	
	public synchronized void close()
		{
		LOG.info("closing");
		if(this.snpDatabase!=null)
			{
			try { this.snpDatabase.close();}
			catch(Throwable err) {}
			this.snpDatabase=null;
			}
		
		if(this.json2json!=null)
			{
			try { this.json2json.close();}
			catch(Throwable err){}
			this.json2json=null;
			}
		if(this.environment!=null)
			{
			if( !this.dropEnvironmentOnClose &&
				!this.environment.getConfig().getReadOnly())
				{
				try { this.environment.cleanLog();}
				catch(Throwable err){}
				}
			try { this.environment.close();}
			catch(Throwable err){}
			this.environment=null;
			}
		if(this.dbHome!=null)
			{
			if(this.dropEnvironmentOnClose)
				{
				for(File f:this.dbHome.listFiles())
					{
					LOG.info("delete "+f);
					f.delete();
					}
				LOG.info("delete "+this.dbHome);
				this.dbHome.delete();
				}
			this.dropEnvironmentOnClose=false;
			this.dbHome=null;
			}
		}
	}
